Support using composefs signatures also with bootc commits
authorAlexander Larsson <alexl@redhat.com>
Mon, 15 Sep 2025 09:33:16 +0000 (11:33 +0200)
committerAlexander Larsson <alexl@redhat.com>
Mon, 15 Sep 2025 12:34:12 +0000 (14:34 +0200)
When using bootc, if you convert a signed ostree commit into an OCI
image `rpm-ostree compose container-encapsulate` you end up with a new
commit that isn't signed. However, the base commit object, and its
commitmeta are still in the image and will end up the repo, and
since https://github.com/bootc-dev/bootc/pull/1600 the base commit
id is available as the parent commit.

So, we change ostree-prepare-root to fall back to using the base
commit+commitmeta to find the expected composefs digest if the main
commit is not signed.

Note: This will only work with ostree-only commits. If you have any
layered data, then the content will change, and the composefs digest
in the base commit will not match the deployed one. This is expected
with such sealed commits though. If you want to layer, either disable
sealing, or create a new sealed ostree commit for the new image.

src/libotcore/otcore-prepare-root.c

index 6ca42afc28ae6d77e1ef6bbe16649ac918fcbc18..a628b88030d949539690bb037560ede32c850bea 100644 (file)
@@ -305,11 +305,41 @@ load_variant (const char *root_mountpoint, const char *digest, const char *exten
   return TRUE;
 }
 
+/* For local bootc commit, return the base ostree commit that was used to generate the commit.
+ */
+static char *
+get_base_digest_for_bootc_commit (GVariant *commit)
+{
+  g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0);
+
+  /* Check for ostree.container.image-config to determine if this is a bootc commit */
+  const char *image_config = NULL;
+  if (!g_variant_lookup (metadata, "ostree.container.image-config", "s", &image_config))
+    return NULL;
+
+  /* If so, since https://github.com/bootc-dev/bootc/pull/1600, the
+   * parent commit will be the base ostree commit. */
+
+  g_autoptr (GVariant) parent_commit_v = g_variant_get_child_value (commit, 1);
+  if (g_variant_n_children (parent_commit_v) != OSTREE_SHA256_DIGEST_LEN)
+    return NULL;
+
+  const guint8 *parent_commit_bin = ot_variant_get_data (parent_commit_v, NULL);
+  if (parent_commit_bin == NULL)
+    return NULL;
+
+  char *basecommit_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1);
+  ot_bin2hex (basecommit_digest, parent_commit_bin, OSTREE_SHA256_DIGEST_LEN);
+
+  return basecommit_digest;
+}
+
 // Given a mount point, directly load the .commit object.  At the current time this tool
 // doesn't link to libostree.
 static gboolean
 load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out,
-                        GVariant **commitmeta_out, GError **error)
+                        GVariant **commitmeta_out, GVariant **basecommit_out,
+                        GVariant **basecommitmeta_out, GError **error)
 {
   g_autofree char *digest = g_path_get_basename (deploy_path);
   char *dot = strchr (digest, '.');
@@ -318,6 +348,8 @@ load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GV
 
   g_autoptr (GVariant) commit_v = NULL;
   g_autoptr (GVariant) commitmeta_v = NULL;
+  g_autoptr (GVariant) basecommit_v = NULL;
+  g_autoptr (GVariant) basecommitmeta_v = NULL;
 
   if (!load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, FALSE,
                      &commit_v, error))
@@ -327,8 +359,28 @@ load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GV
                      &commitmeta_v, error))
     return FALSE;
 
+  /* In case the commit is one created by bootc when importing a container, it will not
+   * be signed. However, we can still look at the base commit which may be signed.
+   */
+  g_autofree char *basecommit_digest = get_base_digest_for_bootc_commit (commit_v);
+  if (basecommit_digest)
+    {
+      if (!load_variant (root_mountpoint, basecommit_digest, "commit",
+                         OSTREE_COMMIT_GVARIANT_FORMAT, TRUE, &basecommit_v, error))
+        return FALSE;
+
+      if (basecommit_v != NULL)
+        {
+          if (!load_variant (root_mountpoint, basecommit_digest, "commitmeta",
+                             G_VARIANT_TYPE ("a{sv}"), TRUE, &basecommitmeta_v, error))
+            return FALSE;
+        }
+    }
+
   *commit_out = g_steal_pointer (&commit_v);
   *commitmeta_out = g_steal_pointer (&commitmeta_v);
+  *basecommit_out = g_steal_pointer (&basecommit_v);
+  *basecommitmeta_out = g_steal_pointer (&basecommitmeta_v);
 
   return TRUE;
 }
@@ -556,14 +608,30 @@ otcore_mount_rootfs (RootConfig *rootfs_config, GVariantBuilder *metadata_builde
   if (rootfs_config->is_signed)
     {
       const char *composefs_pubkey = rootfs_config->signature_pubkey;
-      g_autoptr (GVariant) commit = NULL;
-      g_autoptr (GVariant) commitmeta = NULL;
-
-      if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta,
-                                   error))
+      g_autoptr (GVariant) main_commit = NULL;
+      g_autoptr (GVariant) main_commitmeta = NULL;
+      g_autoptr (GVariant) base_commit = NULL;
+      g_autoptr (GVariant) base_commitmeta = NULL;
+      GVariant *commit = NULL;
+      GVariant *commitmeta = NULL;
+
+      if (!load_commit_for_deploy (root_mountpoint, deploy_path, &main_commit, &main_commitmeta,
+                                   &base_commit, &base_commitmeta, error))
         return glnx_prefix_error (error, "Error loading signatures from repo");
 
-      if (commitmeta == NULL)
+      if (main_commitmeta != NULL)
+        {
+          commit = main_commit;
+          commitmeta = main_commitmeta;
+        }
+      else if (base_commitmeta != NULL)
+        {
+          ot_journal_print (LOG_INFO,
+                            "composefs+ostree: Validating composefs using bootc base commit");
+          commit = base_commit;
+          commitmeta = base_commitmeta;
+        }
+      else
         return glnx_throw (error, "No commitmeta for deploy %s", deploy_path);
 
       g_autoptr (GVariant) signatures = g_variant_lookup_value (